package com.citydo.webrtcspringboot.websocket;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.citydo.webrtcspringboot.config.ConfiguratorForClientIp;
import com.citydo.webrtcspringboot.entity.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Data 由于@Data重写了hashCode()和equals()方法，
 * 会导致Set<Connection> remove元素时，找不到正确的元素，
 * 应用@Setter @Getter @ToString替换
 * @ServerEndpoint 不是单例模式
 */
@ServerEndpoint(value = "/", configurator = ConfiguratorForClientIp.class)
@Component
@Slf4j
@Getter
@Setter
@ToString
public class Connection {
    private static Map<Session, Set<String>> clients = new HashMap<>();
    private static Map<String, List<Session>> connectionPair = new HashMap<>();


    private static volatile AtomicInteger onlineCount = new AtomicInteger(0);   //在线总人数
    private String ip;  //某个客户端的ip
    private Session session;    //与某个客户端的连接会话，需要通过它来给客户端发送数据

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        ip = (String) session.getUserProperties().get("clientIp");
        clients.put(session, new HashSet<>());
        System.out.println("用户: " + ip + " 会话：" + session.getId() + ", 连接到服务器,当前在线人数为：" + onlineCount.incrementAndGet());
    }
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        Set<String> connectionIds = clients.get(session);
        for (String connectionId : connectionIds) {
            List<Session> pair = connectionPair.get(connectionId);
            if (pair != null) {
                Session otherSession = pair.get(0);
                if (otherSession == this.session) {
                    otherSession = pair.get(1);
                }
                if (otherSession != null) {
                    JSONObject obj = new JSONObject();
                    obj.put("type", "disconnect");
                    obj.put("connectionId", connectionId);
                    Connection.sendJson(otherSession, obj.toJSONString());
                }
            }
            connectionPair.remove(connectionId);
        }
        clients.remove(session);
        //在线数减1
        System.out.println("用户: " + ip + " 会话：" + session.getId() + ", 关闭连接，当前在线人数为: " + onlineCount.addAndGet(-1));
    }

    /**
     * 连接发生错误时调用的方法
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("用户连接错误: {}",ip);
        error.printStackTrace();
    }

    /**
     * 收到客户端消息后调用的方法
     * @param stringMessage 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(Session session, String stringMessage) {
        Message message = JSON.parseObject(stringMessage, Message.class);
        switch (message.getType()) {
            case Message.TYPE_COMMAND_VALIDATION:
                onValidation(message);
                break;
            case Message.TYPE_COMMAND_CONNECT:
                onConnect(message.getConnectionId());
                break;
            case Message.TYPE_COMMAND_DISCONNECT:
                onDisconnect(message.getConnectionId());
                break;
            case Message.TYPE_COMMAND_OFFER:
                onOffer(message.getData());
                break;
            case Message.TYPE_COMMAND_ANSWER:
                onAnswer(message.getData());
                break;
            case Message.TYPE_COMMAND_CANDIDATE:
                onCandidate(message.getData());
                break;
            default:
        }
    }

    private void onConnect(String cid) {
        Boolean polite = true;
        Set<String> connectionIds = getOrCreateConnectionIds(this.session);
        connectionIds.add(cid);
        JSONObject obj = new JSONObject();
        obj.put("type", "connect");
        obj.put("connectionId", cid);
        obj.put("polite", polite);
        Connection.sendJson(this.session, obj.toJSONString());
   }

    private void onDisconnect(String cid) {
        Set<String> connectionIds = getOrCreateConnectionIds(this.session);
        connectionIds.remove(cid);
        JSONObject obj = new JSONObject();
        obj.put("type", "disconnect");
        obj.put("connectionId", cid);
        List<Session> pair = connectionPair.get(cid);
        if (pair != null) {
            Session otherSession = pair.get(0);
            if (otherSession == this.session) {
                otherSession = pair.get(1);
            }
            if (otherSession != null) {
                Connection.sendJson(otherSession, obj.toJSONString());
            }
        }
        connectionPair.remove(cid);
        Connection.sendJson(this.session, obj.toJSONString());
    }

    private void onOffer(String data){
        SData sd = JSON.parseObject(data, SData.class);
        String connectionId = sd.getConnectionId();
        Offer newOffer = new Offer(sd.getSdp(), System.currentTimeMillis(), false);
        List<Session> lst = new ArrayList<>();
        lst.add(this.session);
        connectionPair.put(connectionId, lst);

        System.out.println("收到申请，会话：" + this.session.getId() + "-> " + data);
        for (Map.Entry<Session, Set<String>> entry : clients.entrySet()) {
            Session k = entry.getKey();
            if (k == this.session) {
                continue;
            }
            JSONObject obj = new JSONObject();
            JSONObject dt = new JSONObject();
            dt.put("sdp", newOffer.getSdp());
            dt.put("datetime", newOffer.getDatetime());
            dt.put("polite", newOffer.getPolite());
            obj.put("data", dt);
            obj.put("from", connectionId);
            obj.put("to", "");
            obj.put("type", "offer");
            String output = obj.toJSONString();
            Connection.sendJson(k, output);
            System.out.println(this.session.getId() + "-> " + output);
        }
    }

    private void onAnswer(String data) {
        SData sd = JSON.parseObject(data, SData.class);
        String connectionId = sd.getConnectionId();
        Set<String> connectionIds = getOrCreateConnectionIds(this.session);
        connectionIds.add(connectionId);
        Answer newAnswer = new Answer(sd.getSdp(), System.currentTimeMillis());
        List<Session> pair = connectionPair.get(connectionId);
        if (pair == null) {
            return;
        }
        Session otherSession = pair.get(0);
        if (otherSession == this.session) {
            otherSession = pair.get(1);
        }
        List<Session> lst = new ArrayList<>();
        lst.add(otherSession);
        lst.add(this.session);
        connectionPair.put(connectionId, lst);
        JSONObject obj = new JSONObject();
        JSONObject dt = new JSONObject();
        dt.put("sdp", newAnswer.getSdp());
        dt.put("datetime", newAnswer.getDatetime());
        obj.put("data", dt);
        obj.put("from", connectionId);
        obj.put("to", "");
        obj.put("type", "answer");
        String output = obj.toJSONString();
        Connection.sendJson(otherSession, output);
        System.out.println(this.session.getId() + "-> " + output);
    }

    private void onCandidate(String data) {
        SData sd = JSON.parseObject(data, SData.class);
        String connectionId = sd.getConnectionId();
        Candidate candidate = new Candidate(sd.getCandidate(), sd.getSdpMLineIndex(), sd.getSdpMid(), System.currentTimeMillis());
        for (Map.Entry<Session, Set<String>> entry : clients.entrySet()) {
            Session k = entry.getKey();
            if (k == this.session) {
                continue;
            }
            JSONObject obj = new JSONObject();
            JSONObject dt = new JSONObject();
            dt.put("candidate", candidate.getCandidate());
            dt.put("sdpMLineIndex", candidate.getSdpMLineIndex());
            dt.put("sdpMid", candidate.getSdpMid());
            dt.put("datetime", candidate.getDatetime());
            obj.put("data", dt);
            obj.put("from", connectionId);
            obj.put("to", "");
            obj.put("type", "candidate");
            String output = obj.toJSONString();
            Connection.sendJson(k, output);
            System.out.println(this.session.getId() + "-> " + output);
        }
    }


    private void onValidation(Message message){
        String token = message.getToken();
        if (token != null){
            if(!token.equals("admin")){
                try {
                    JSONObject obj = new JSONObject();
                    obj.put("type", "validation");
                    obj.put("message", "连接失败");
                    Connection.sendJson(this.session, obj.toJSONString());
                    this.session.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }else{
            if(!ip.equals("127.0.0.12")) {
                try {
                    JSONObject obj = new JSONObject();
                    obj.put("type", "validation");
                    obj.put("message", "连接失败");
                    Connection.sendJson(this.session, obj.toJSONString());
                    this.session.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

            }
        }
    }

    private Set<String> getOrCreateConnectionIds(Session session) {
        Set<String> connectionIds = null;
        if (clients.get(session) == null){
            connectionIds = new HashSet<>();
            clients.put(session, connectionIds);
        }
        connectionIds = clients.get(session);
        System.out.println("获取或添加会话：" + session + " " + connectionIds);
        return connectionIds;
    }

    private static String now(){
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = new Date(System.currentTimeMillis());
        return formatter.format(date);
    }

    private static void sendJson(Session session, String msg) {
        try {
            JSONObject obj = JSON.parseObject(msg);
            session.getBasicRemote().sendText(JSON.toJSONString(obj));
            System.out.println(JSON.toJSONString(obj));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
